import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
import type { RequestConfig, RequestOptions, UploadFileParams } from './type'
import axios from 'axios'
import qs from 'qs'
import { AxiosCanceler } from './cancel'
import { AxiosRetry } from './retry'
import { ContentTypeEnum, RequestEnum } from './type'

export * from './type'

// 请求中的接口缓存, 用来记录是否关闭动画
const REQUEST_MAP_CACHE = new Map()

/**
 * @description  axios module
 */
export class Request {
  private axiosInstance: AxiosInstance
  private requestConfig!: AxiosRequestConfig
  private requestOptions!: RequestOptions

  constructor(requestConfig?: AxiosRequestConfig, requestOptions?: RequestOptions) {
    // 设置配置
    this.setConfig(requestConfig, requestOptions)
    // 创建实例
    this.axiosInstance = axios.create(this.requestConfig)
    // 拦截处理
    this.setupInterceptors()
  }

  /**
   * @description 设置配置
   */
  public setConfig(requestConfig?: AxiosRequestConfig, requestOptions?: RequestOptions) {
    this.requestConfig = {
      baseURL: requestConfig?.baseURL, // 默认地址
      timeout: 1000 * 60, // 默认超时时间
      ...requestConfig
    }
    this.requestOptions = {
      clearEmptyData: true, // 清除空参数
      openLoad: () => {},
      closeLoad: () => {},
      fullPath: false, // 是否是完整路径 如果是完整路径 不会拼接baseURL
      globAlToast: true, // 是否全局提示语
      ignoreCancel: true, // 忽略重复请求
      isReturnNativeResponse: false, // 是否返回原生响应头 比如：需要获取响应头时使用该属性
      loadingFlag: true, // 是否开启加载动画的标志 默认是开启
      loadingMask: true, // 加载动画是否添加蒙版
      loadingText: '', // 加载文案
      needTimestamp: true, // 是否加时间戳
      retryRequest: {
        count: 5,
        isOpenRetry: false,
        waitTime: 100
      },
      ...requestOptions
    }
  }

  /**
   * @description 设置通用请求头
   */
  setHeader(headers: any): void {
    if (!this.axiosInstance) return
    Object.assign(this.axiosInstance.defaults.headers, headers)
  }

  /**
   * @description  上传文件
   */
  uploadFile<T = any>(config: AxiosRequestConfig, params: UploadFileParams, options: RequestOptions) {
    const formData = new window.FormData()
    const customFilename = params.name || 'file'

    if (params.filename) {
      formData.append(customFilename, params.file, params.filename)
    } else {
      formData.append(customFilename, params.file)
    }

    if (params.data) {
      Object.keys(params.data).forEach((key) => {
        const value = params.data![key]
        if (Array.isArray(value)) {
          value.forEach((item) => {
            formData.append(`${key}[]`, item)
          })
          return
        }
        formData.append(key, params.data![key])
      })
    }

    return this.request<T>(
      {
        ...config,
        data: formData,
        headers: {
          'Content-type': ContentTypeEnum.FORM_DATA
        },
        method: 'POST'
      },
      options
    )
  }

  /**
   * @description 拦截器配置
   */
  private setupInterceptors() {
    // 取消请求对象
    const axiosCanceler = new AxiosCanceler()

    // 请求拦截器配置处理
    this.axiosInstance.interceptors.request.use(
      (config: InternalAxiosRequestConfig) => {
        const requestOptions: RequestOptions = (config as RequestConfig).requestOptions || this.requestOptions
        if (requestOptions.loadingFlag) {
          // 标记当前请求的接口地址
          REQUEST_MAP_CACHE.set(config.url, config.url)
          requestOptions.openLoad && requestOptions.openLoad()
        }
        // 是否忽略取消请求
        const ignoreCancel = requestOptions.ignoreCancel
        !ignoreCancel && axiosCanceler.addPending(config)
        return config
      },
      (error) => {
        return Promise.reject(error)
      }
    )

    // 响应结果拦截器处理
    this.axiosInstance.interceptors.response.use(
      (response: AxiosResponse<any>) => {
        response && axiosCanceler.removePending(response.config)
        const requestOptions: RequestOptions = (response.config as any).requestOptions || this.requestOptions
        // 关闭加载动画函数
        REQUEST_MAP_CACHE.delete(response.config.url!)
        setTimeout(() => {
          if (REQUEST_MAP_CACHE.size <= 0) {
            requestOptions.closeLoad && requestOptions.closeLoad()
          }
        })
        return response
      },
      (error) => {
        // 添加自动重试机制 保险起见 只针对GET请求
        const retryRequest = new AxiosRetry()
        const requestOptions = error.config?.requestOptions || {}
        const { isOpenRetry } = requestOptions.retryRequest
        error.config.method?.toUpperCase() === RequestEnum.GET && isOpenRetry && retryRequest.retry(this.axiosInstance, error)

        // 关闭加载动画函数
        REQUEST_MAP_CACHE.delete(error.config.url)
        setTimeout(() => {
          if (REQUEST_MAP_CACHE.size <= 0) {
            requestOptions.closeLoad && requestOptions.closeLoad()
          }
        })
        return Promise.reject(error)
      }
    )
  }

  /**
   * 获取axios实例
   * @returns {AxiosInstance} axios实例
   */
  public getAxiosInstance() {
    return this.axiosInstance
  }

  /**
   * 获取请求参数
   * @returns {AxiosInstance} 请求配置
   */
  public getRequestConfig() {
    return this.requestConfig
  }

  /**
   * 获取请求配置
   * @returns 请求配置
   */
  public getRequestOptions() {
    return this.requestOptions
  }

  /**
   * 添加请求地址时间戳
   * @param url
   */
  addUrlTimestamp(url: string) {
    if (!url) return url
    url = url.includes('?') ? `${url}&t=${Date.now()}` : `${url}?t=${Date.now()}`
    return url
  }

  delete<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
    return this.request({ ...config, method: 'DELETE' }, options)
  }

  get<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
    return this.request({ ...config, method: 'GET' }, options)
  }

  /**
   * 请求接口传参处理
   * @param params 请求接口的传参
   */
  handleData<T extends { string: any }>(params: any): T {
    const data = Object.create(null)
    // 过滤掉空传参 (undefined || '' || null)
    Object.keys(params).forEach((k) => {
      if (params[k] !== undefined && params[k] !== null && String(params[k]) !== '') {
        data[k] = params[k]
      }
    })
    return data
  }

  post<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
    return this.request({ ...config, method: 'POST' }, options)
  }

  put<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
    return this.request({ ...config, method: 'PUT' }, options)
  }

  request<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
    const { closeLoad, openLoad } = this.requestOptions // 先拿出来，防止JSON.parse(JSON.stringify()) 清空
    const requestConfig = { ...JSON.parse(JSON.stringify(this.requestConfig)), ...config } as RequestConfig
    const requestOptions = { ...JSON.parse(JSON.stringify(this.requestOptions)), closeLoad, openLoad, ...options } as RequestOptions
    requestConfig.requestOptions = requestOptions
    // 请求类型参数处理
    if (requestConfig.method === RequestEnum.GET) {
      requestConfig.params = requestConfig.params || requestConfig.data || null
      requestConfig.data = null
    } else {
      const contentType = requestConfig.headers ? requestConfig.headers['Content-Type'] || requestConfig.headers['content-type'] : ''
      if (contentType && contentType.includes(ContentTypeEnum.FORM_URLENCODED)) {
        requestConfig.data = requestConfig.data ? (typeof requestConfig.data === 'string' ? requestConfig.data : qs.stringify(requestConfig.data)) : null
      } else {
        requestConfig.data = requestConfig.data || null
      }
    }

    // 添加请求地址时间戳
    if (requestOptions.needTimestamp === undefined || requestOptions.needTimestamp) {
      requestConfig.url = this.addUrlTimestamp(requestConfig.url as string)
    }

    // 过滤对象类型的空值 null,undefined,''
    if (Object.prototype.toString.call(requestConfig.data) === '[object Object]' && requestOptions.clearEmptyData) {
      requestConfig.data = requestConfig.data ? this.handleData(requestConfig.data) : {}
    }

    // 设置基础url
    if (requestOptions.fullPath) {
      requestConfig.baseURL = ''
    }

    // 处理没有headers字段
    requestConfig.headers = requestConfig.headers || {}

    return this.axiosInstance
      .request<any, AxiosResponse<any>>(requestConfig)
      .then((res: AxiosResponse<any>) => {
        if (res && res.status === 200) {
          try {
            res.data = JSON.parse(res.data)
            return requestOptions.isReturnNativeResponse ? res : res.data
          } catch {
            return requestOptions.isReturnNativeResponse ? res : res.data
          }
        }
      })
      .catch((error: Error) => {
        if (axios.isAxiosError(error)) {
          // 在这里重写axios的错误消息
        }
        throw error
      })
  }
}
